package com.open.lock.aspect;

import com.open.lock.annotation.Idempotent;
import com.open.lock.component.CloudLock;
import com.open.lock.enums.LockModel;
import com.open.lock.exception.IdempotentException;
import com.open.lock.utils.LockUtil;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RLock;
import org.springframework.beans.factory.annotation.Autowired;

import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;

/**
 * @author zhang kaichao
 */
@Aspect
public class IdempotentAspect {

    @Autowired
    private CloudLock cloudLock;

    @Pointcut("@annotation(com.open.lock.annotation.Idempotent)")
    public void pointCut() {
    }

    @Around("pointCut()")
    public Object aroundPointCut(ProceedingJoinPoint joinPoint) throws Throwable {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        Idempotent idempotent = method.getAnnotation(Idempotent.class);
        String[] keys = idempotent.keys();
        String info = idempotent.info();
        String key = LockUtil.createKey(joinPoint, keys);
        if (key == null || "".equals(key)) {
            throw new IdempotentException(info);
        }
        long leaseTime = idempotent.expire();
        LockModel lockModel = idempotent.lockModel();
        boolean unlock = idempotent.unlock();
        RLock rLock = cloudLock.getIdempotentLock(lockModel, key);
        boolean lock = rLock.tryLock(0, leaseTime, TimeUnit.MILLISECONDS);
        if (lock) {
            try {
                return joinPoint.proceed();
            } finally {
                if (unlock || leaseTime < 0) {
                    cloudLock.unlock(rLock);
                }
            }
        }
        throw new IdempotentException(info);
    }

}
